package com.ibeeking.gmc.global.g.rest.service.impl;


import com.baomidou.dynamic.datasource.toolkit.CryptoUtils;
import com.ibeeking.found.common.db.util.DBPasswordGenerateUtil;
import com.ibeeking.found.common.entity.DatasourceConfigDTO;
import com.ibeeking.gmc.global.g.rest.service.IDatabaseTemplateService;
import com.ibeeking.gmc.global.g.rest.service.IDatasourceConfigService;
import com.ibeeking.found.common.base.BaseDO;
import com.ibeeking.found.common.enums.PublishStatusEnum;
import com.ibeeking.found.common.utils.UserUtils;
import com.ibeeking.gmc.global.service.common.bo.DatasourceConfigBO;
import com.ibeeking.gmc.global.service.common.constants.RedisKeyConstant;
import com.ibeeking.gmc.global.service.common.dos.DatasourceConfigDO;
import com.ibeeking.gmc.global.service.common.dos.TenantDatasourceDO;
import com.ibeeking.gmc.global.service.common.enums.DatasourceTypeEnum;
import com.ibeeking.gmc.global.service.common.param.DatasourceConfigParam;
import com.ibeeking.gmc.global.service.common.param.DatasourceConfigTestParam;
import com.ibeeking.gmc.global.service.common.query.DatasourceConfigPageQuery;
import com.ibeeking.gmc.global.service.common.query.DatasourceConfigQuery;
import com.ibeeking.gmc.global.service.common.vo.DatasourceConfigPageVO;
import com.ibeeking.gmc.global.service.common.vo.DatasourceConfigVO;
import com.ibeeking.gmc.global.service.mapper.DatasourceConfigMapper;
import com.ibeeking.gmc.global.service.mapper.TenantDatasourceMapper;
import com.ibeeking.nematos.constants.enums.LogClassifyEnum;
import com.ibeeking.nematos.core.utils.SpringBeanUtil;
import com.ibeeking.nematos.exception.BusinessException;
import com.ibeeking.nematos.log.utils.LogUtils;
import com.ibeeking.nematos.redis.annotation.CacheQuery;
import com.ibeeking.nematos.utils.bean.BeanUtil;
import com.ibeeking.nematos.utils.data.Collections3;
import com.ibeeking.nematos.utils.data.StringUtils;
import com.ibeeking.nematos.utils.json.JsonUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @ClassName DatabaseTemplateServiceImpl
 * @Description
 * @Author ibeeking
 * @Date 2021-03-02 09:07
 **/
@Service
public class DatasourceConfigServiceImpl extends ServiceImpl<DatasourceConfigMapper, DatasourceConfigDO> implements IDatasourceConfigService {

    @Resource
    private DatasourceConfigMapper datasourceConfigMapper;

    @Resource
    private IDatabaseTemplateService templateDatabaseServiceImpl;

    @Resource
    private TenantDatasourceMapper tenantDatasourceMapper;

    @Override
    public Page<DatasourceConfigPageVO> pageList(DatasourceConfigPageQuery query) {
        LambdaQueryWrapper<DatasourceConfigDO> queryWrapper = new LambdaQueryWrapper<DatasourceConfigDO>()
                .orderByAsc(DatasourceConfigDO::getSort)
                .orderByDesc(BaseDO::getCreateTime)
                .eq(null != query.getType(), DatasourceConfigDO::getType, query.getType())
                .eq(null != query.getPublishStatus(), DatasourceConfigDO::getPublishStatus, query.getPublishStatus())
                .eq(null != query.getName(), DatasourceConfigDO::getName, query.getName())
                .eq(StringUtils.isNotBlank(query.getHost()), DatasourceConfigDO::getHost, query.getHost());
        Page<DatasourceConfigDO> pages = datasourceConfigMapper.selectPage(new Page<>(query.getPageNum(), query.getPageSize()), queryWrapper);
        Page<DatasourceConfigPageVO>  resultVOS = BeanUtil.convertPage(pages, DatasourceConfigPageVO.class);
        if(resultVOS!=null || resultVOS.getSize() > 0){
            List<DatasourceConfigPageVO> resultListVOS = resultVOS.getRecords();
            resultListVOS.stream().forEach(m->{
                try{
                    m.setHost(CryptoUtils.decrypt(m.getHost()));
                    m.setUserName(CryptoUtils.decrypt(m.getUserName()));
                    m.setPassword(CryptoUtils.decrypt(m.getPassword()));
                }catch(Exception ex){
                    throw new BusinessException("数据源用户名或者密码解密出错！");
                }
            });
            resultVOS.setRecords(resultListVOS);
        }
        return resultVOS;
    }

    @Override
    public Boolean add(DatasourceConfigParam param) {
        Connection connection = getConnection(param.getHost(), param.getPort(), param.getUserName(), param.getPassword());
        if (null == connection) {
            throw new BusinessException("数据源配置有误");
        }
        try {
            connection.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        DatasourceConfigDO templateDatabaseDO = BeanUtil.convertBean(param, DatasourceConfigDO.class);
        checkUnique(templateDatabaseDO);
        if(StringUtils.isEmpty(param.getUserName()) || StringUtils.isEmpty(param.getPassword())){
            throw new BusinessException("数据源用户名或者密码为空");
        }else{
            try{
                templateDatabaseDO.setHost(CryptoUtils.encrypt(param.getHost()));
                templateDatabaseDO.setUserName(CryptoUtils.encrypt(param.getUserName()));
                templateDatabaseDO.setPassword(CryptoUtils.encrypt(param.getPassword()));
            }catch(Exception ex){
                throw new BusinessException("数据源用户名或者密码加密出错！");
            }
        }
        templateDatabaseDO.setCreateBy(UserUtils.getUserId());
        templateDatabaseDO.setCreateTime(LocalDateTime.now());
        templateDatabaseDO.setModifyTime(LocalDateTime.now());
        return datasourceConfigMapper.insert(templateDatabaseDO) > 0;
    }

    @Override
    public Boolean modify(DatasourceConfigParam param) {
        Connection connection = getConnection(param.getHost(), param.getPort(), param.getUserName(), param.getPassword());
        if (null == connection) {
            throw new BusinessException("数据源配置有误");
        }
        try {
            connection.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        DatasourceConfigDO templateDatabaseDO = BeanUtil.convertBean(param, DatasourceConfigDO.class);
        checkUnique(templateDatabaseDO);
        if(StringUtils.isEmpty(param.getUserName()) || StringUtils.isEmpty(param.getPassword())){
            throw new BusinessException("数据源用户名或者密码为空");
        }else{
            try{
                templateDatabaseDO.setHost(CryptoUtils.encrypt(param.getHost()));
                templateDatabaseDO.setUserName(CryptoUtils.encrypt(param.getUserName()));
                templateDatabaseDO.setPassword(CryptoUtils.encrypt(param.getPassword()));
            }catch(Exception ex){
                throw new BusinessException("数据源用户名或者密码加密出错！");
            }
        }
        templateDatabaseDO.setModifyBy(UserUtils.getUserId());
        templateDatabaseDO.setModifyTime(LocalDateTime.now());
        return datasourceConfigMapper.updateById(templateDatabaseDO) > 0;
    }

    @Override
    public DatasourceConfigVO view(Long id) {
        DatasourceConfigBO configBO = getBean().queryById(id);
        DatasourceConfigVO resultVO  = BeanUtil.convertBean(configBO, DatasourceConfigVO.class);
        return resultVO;
    }

    @Override
    public DatasourceConfigDTO viewById(Long id) {
        DatasourceConfigDO configDO = datasourceConfigMapper.selectById(id);
        DatasourceConfigDTO resultDTO  =  BeanUtil.convertBean(configDO, DatasourceConfigDTO.class);
        if(resultDTO !=null) {
            try {
                resultDTO.setHost(CryptoUtils.decrypt(configDO.getHost()));
                resultDTO.setUserName(CryptoUtils.decrypt(configDO.getUserName()));
                resultDTO.setPassword(CryptoUtils.decrypt(configDO.getPassword()));
            } catch (Exception ex) {
                throw new BusinessException("数据源用户名或者密码解密出错！");
            }
        }
        return resultDTO;
    }

    @CacheQuery(cacheName = RedisKeyConstant.TENANT_DATASOURCE, key = "#tenantId", expires = RedisKeyConstant.MONTH)
    @Override
    public DatasourceConfigDTO queryTenantDatasource(Integer tenantId) {
        TenantDatasourceDO tenantDatasourceDO =  tenantDatasourceMapper.selectOne(new LambdaQueryWrapper<TenantDatasourceDO>()
                        .eq(TenantDatasourceDO::getTenantId, tenantId));
        if(tenantDatasourceDO == null) return null;
        DatasourceConfigDO configDO = datasourceConfigMapper.selectById(tenantDatasourceDO.getDatasourceId());
        if(configDO == null) return null;
        DatasourceConfigDTO resultDTO  = BeanUtil.convertBean(configDO, DatasourceConfigDTO.class);
        if(resultDTO !=null) {
            try {
                resultDTO.setHost(CryptoUtils.decrypt(configDO.getHost()));
                resultDTO.setUserName(CryptoUtils.decrypt(configDO.getUserName()));
                resultDTO.setPassword(CryptoUtils.decrypt(configDO.getPassword()));
            } catch (Exception ex) {
                throw new BusinessException("数据源用户名或者密码解密出错！");
            }
        }
        return resultDTO;
    }

    @Override
    public DatasourceConfigBO queryById(Long datasourceConfigId) {
        DatasourceConfigDO configDO = datasourceConfigMapper.selectById(datasourceConfigId);
        DatasourceConfigBO resultBO  = BeanUtil.convertBean(configDO, DatasourceConfigBO.class);
        if(resultBO !=null) {
            try {
                resultBO.setHost(CryptoUtils.decrypt(configDO.getHost()));
                resultBO.setUserName(CryptoUtils.decrypt(configDO.getUserName()));
                resultBO.setPassword(CryptoUtils.decrypt(configDO.getPassword()));
            } catch (Exception ex) {
                throw new BusinessException("数据源用户名或者密码解密出错！");
            }
        }
        return resultBO;
    }

    @Override
    public Boolean changeStatus(Long id, Integer status) {
        DatasourceConfigDO templateDatabaseDO = new DatasourceConfigDO();
        templateDatabaseDO.setId(id);
        templateDatabaseDO.setPublishStatus(status);
        templateDatabaseDO.setModifyBy(UserUtils.getUserId());
        templateDatabaseDO.setModifyTime(LocalDateTime.now());
        return datasourceConfigMapper.updateById(templateDatabaseDO) > 0;
    }

    @Transactional(rollbackFor = {Exception.class})
    @Override
    public Boolean del(Long id) {
        //判断是否有子项
        Boolean hasChild = templateDatabaseServiceImpl.hasChild(id);
        if (!hasChild) {
            //删除当前数据源配置
            datasourceConfigMapper.deleteById(id);
        }
        return false;
    }

    @Override
    public List<DatasourceConfigVO> list(DatasourceConfigQuery query) {
        List<DatasourceConfigVO> resultVOS = null;
        LambdaQueryWrapper<DatasourceConfigDO> queryWrapper = new LambdaQueryWrapper<DatasourceConfigDO>()
                .orderByAsc(DatasourceConfigDO::getSort)
                .orderByDesc(BaseDO::getCreateTime)
                .eq(query.getType()!=null, DatasourceConfigDO::getType, query.getType())
                .eq(DatasourceConfigDO::getPublishStatus, PublishStatusEnum.ENABLE.getValue())
                .eq(StringUtils.isNotBlank(query.getHost()), DatasourceConfigDO::getHost, query.getHost());
        List<DatasourceConfigDO> datasourceConfigDOS = datasourceConfigMapper.selectList(queryWrapper);
        if(Collections3.isNotEmpty(datasourceConfigDOS)) {
            resultVOS = datasourceConfigDOS.stream().map(m->{
                DatasourceConfigVO entityVO = BeanUtil.convertBean(m, DatasourceConfigVO.class);
                try {
                    entityVO.setHost(CryptoUtils.decrypt(m.getHost()));
                    entityVO.setUserName(CryptoUtils.decrypt(m.getUserName()));
                    entityVO.setPassword(CryptoUtils.decrypt(m.getPassword()));
                } catch (Exception ex) {
                    throw new BusinessException("数据源用户名或者密码解密出错！");
                }
                return entityVO;
            }).collect(Collectors.toList());
        }
        return resultVOS;
    }

    @Override
    public List<DatasourceConfigBO> selectNameByIds(List<Long> ids) {
        if (Collections3.isEmpty(ids)) {
            return Collections3.emptyList();
        }
        List<DatasourceConfigBO> resultBOS = null;
        LambdaQueryWrapper<DatasourceConfigDO> queryWrapper = new LambdaQueryWrapper<DatasourceConfigDO>()
                .eq(DatasourceConfigDO::getPublishStatus, PublishStatusEnum.ENABLE.getValue())
                .in(BaseDO::getId, ids);
        List<DatasourceConfigDO> datasourceConfigDos = datasourceConfigMapper.selectList(queryWrapper);
        if(Collections3.isNotEmpty(datasourceConfigDos)) {
            resultBOS = datasourceConfigDos.stream().map(m->{
                DatasourceConfigBO entityBO = BeanUtil.convertBean(m, DatasourceConfigBO.class);
                try {
                    entityBO.setHost(CryptoUtils.decrypt(m.getHost()));
                    entityBO.setUserName(CryptoUtils.decrypt(m.getUserName()));
                    entityBO.setPassword(CryptoUtils.decrypt(m.getPassword()));
                } catch (Exception ex) {
                    throw new BusinessException("数据源用户名或者密码解密出错！");
                }
                return entityBO;
            }).collect(Collectors.toList());
        }
        return resultBOS;
    }

    @Override
    public Boolean connectionTest(Long id, DatasourceConfigTestParam param) {
        if (null != id) {
            DatasourceConfigBO datasourceConfigBO = getBean().queryById(id);
            param = new DatasourceConfigTestParam();
            param.setHost(datasourceConfigBO.getHost());
            param.setPort(datasourceConfigBO.getPort());
            param.setUserName(datasourceConfigBO.getUserName());
            param.setPassword(datasourceConfigBO.getPassword());
        }
        // 定义连接
        Connection conn = getConnection(param.getHost(), param.getPort(), param.getUserName(), param.getPassword());
        if (null == conn) {
            return false;
        }
        try {
            conn.close();
        } catch (SQLException e) {
            LogUtils.error(LogClassifyEnum.ERROR_LOG, "关闭数据库连接失败", e);
            return false;
        }
        return true;
    }

    @Override
    public DatasourceConfigBO queryDatabseTemplateDatasource(){
        LambdaQueryWrapper<DatasourceConfigDO> queryWrapper = new LambdaQueryWrapper<DatasourceConfigDO>()
                .eq(DatasourceConfigDO::getType, DatasourceTypeEnum.DATASOURCE_TYPE_TEMPLATE.getType())
                .eq(DatasourceConfigDO::getPublishStatus, PublishStatusEnum.ENABLE.getValue());
        DatasourceConfigDO datasourceConfigDO = datasourceConfigMapper.selectOne(queryWrapper);
        DatasourceConfigBO resultBO  = BeanUtil.convertBean(datasourceConfigDO, DatasourceConfigBO.class);
        if(resultBO !=null) {
            try {
                resultBO.setHost(CryptoUtils.decrypt(datasourceConfigDO.getHost()));
                resultBO.setUserName(CryptoUtils.decrypt(datasourceConfigDO.getUserName()));
                resultBO.setPassword(CryptoUtils.decrypt(datasourceConfigDO.getPassword()));
            } catch (Exception ex) {
                throw new BusinessException("数据源用户名或者密码解密出错！");
            }
        }
        return resultBO;
    }

    private void checkUnique(DatasourceConfigDO configDO) {
        Integer count = datasourceConfigMapper.selectCount(new LambdaQueryWrapper<>(DatasourceConfigDO.class)
                .ne(null != configDO.getId(), DatasourceConfigDO::getId, configDO.getId())
                .eq(StringUtils.isNotBlank(configDO.getName()), DatasourceConfigDO::getName, configDO.getName())
        );
        if (count > 0) {
            throw new BusinessException("当前数据源连接已存在");
        }
    }

    public static Connection getConnection(String host, Integer port, String userName, String password) {
        Connection conn;
        try {
            //加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");

            conn = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/gmc_global", userName, password);
            if (null == conn) {
                throw new BusinessException("获取数据源连接失败");
            } else {
                return conn;
            }
        } catch (Exception ex) {
            throw new BusinessException("获取数据源连接失败");
        }
    }

    private static IDatasourceConfigService getBean() {
        return SpringBeanUtil.getBean(IDatasourceConfigService.class);
    }
}
